#include "GTimer.h"
#include "GError.h"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

namespace GUtil
{
	/****************************************************/
	/*						GTicks						*/
	/****************************************************/

	// Description: Per thread ticks per second variable. 
	// Notes1: Thread local storage helps the initializetion.
	// Notes2: Does not change at runtime.
	// -----------------------------------------------------
	G_THREAD_LOCAL GUInt64 GTicks::TicksPerSecond = 0;

	// Description: Constructor
	// Return:
	// Notes1: Starts the counter
	// Notes2: Every thread initializes Ticks per second at first GTicks creation.
	// -----------------------------------------------------
	GTicks::GTicks()
	{
		if (TicksPerSecond == 0)
		{
			TicksPerSecond = GTicks::QueryTickFrequency();
			
			//MemoryBarrier();
		}
		
		// Record starting tick count
		Reset();
	}

	// Description: Destructor
	// Return:
	// Notes:
	// -----------------------------------------------------
	GTicks::~GTicks()
	{
		
	}

	// Description: Calculates the ticks passed since the last Reset.
	// Return: Elapsed ticks
	// Notes:
	// -----------------------------------------------------
	GUInt64 GTicks::GetElapsedTicks() const
	{
		return QueryTickCount() - StartTicks;
	}

	// Description: Resets the timer.
	// Return:
	// Notes:
	// -----------------------------------------------------
	void GTicks::Reset()
	{
		StartTicks = QueryTickCount();
	}

	// Description: Gets the current tick count
	// Return: Tick count
	// Notes:
	// -----------------------------------------------------
	GUInt64 GTicks::QueryTickCount()
	{
		LARGE_INTEGER LargeInt;
		QueryPerformanceCounter(&LargeInt);

		return (GUInt64)LargeInt.QuadPart;
	}

	// Description: Gets the ticks per second.
	// Return: Ticks per second
	// Notes: Used for converting elapsed ticks into seconds
	// -----------------------------------------------------
	GUInt64 GTicks::QueryTickFrequency()
	{
		LARGE_INTEGER LargeInt;
		BOOL Result = QueryPerformanceFrequency(&LargeInt);
		if (Result == 0)
		{
			GError::Error("Hardware does not support high resolution counters.");
			return 0;
		}

		return (GUInt64)LargeInt.QuadPart;
	}

	/****************************************************/
	/*					GTimeRatio						*/
	/****************************************************/

	// Description: Static initializetions for predefined precisions
	// Notes: 
	// -----------------------------------------------------
	const GTimeRatio GTimeRatio::Day = GTimeRatio(86400, 1);
	const GTimeRatio GTimeRatio::Hour = GTimeRatio(3600, 1);
	const GTimeRatio GTimeRatio::Minute = GTimeRatio(60, 1);
	const GTimeRatio GTimeRatio::Second = GTimeRatio(1, 1);
	const GTimeRatio GTimeRatio::MiliSecond = GTimeRatio(1, 1000);
	const GTimeRatio GTimeRatio::MicroSecond = GTimeRatio(1, 1000000);
	const GTimeRatio GTimeRatio::NanoSecond = GTimeRatio(1, 1000000000);

	// Description: Default Constructor
	// Return:
	// Notes: Default precision is seconds
	// -----------------------------------------------------
	GTimeRatio::GTimeRatio()
	{
		Numerator = 1;
		Denominator = 1;
	}

	// Description: Copy constructor
	// Return: 
	// Notes:
	// -----------------------------------------------------
	GTimeRatio::GTimeRatio(const GTimeRatio& Other)
	{
		Numerator = Other.Numerator;
		Denominator = Other.Denominator;
	}

	// Description: Constructor
	// Return: 
	// Notes:
	// -----------------------------------------------------
	GTimeRatio::GTimeRatio(GUInt64 Num, GUInt64 Denom)
	{
		Numerator = Num;
		Denominator = Denom;
	}

	// Description: Destructor
	// Return: 
	// Notes:
	// -----------------------------------------------------
	GTimeRatio::~GTimeRatio()
	{
	}

	// Description: operator for casting to double
	// Return: return the numerator / denominator ratio in double
	// Notes:
	// -----------------------------------------------------
	GTimeRatio::operator double () const
	{
		return (double)Numerator / (double)Denominator;
	}

	// Description: Equality check operator
	// Return: true if both are equal, false otherwise
	// Notes:
	// -----------------------------------------------------
	bool GTimeRatio::operator == (const GTimeRatio& Other)
	{
		return (Numerator = Other.Numerator) && (Denominator == Other.Denominator);
	}

	// Description: Assignment operator
	// Return:
	// Notes:
	// -----------------------------------------------------
	const GTimeRatio& GTimeRatio::operator = (const GTimeRatio& Other)
	{
		Numerator = Other.Numerator;
		Denominator = Other.Denominator;

		return *this;
	}

	/****************************************************/
	/*					GTimeStamp						*/
	/****************************************************/

	// Description: Default constructor
	// Return:
	// Notes: Default is 0 seconds
	// -----------------------------------------------------
	GTimeStamp::GTimeStamp()
	{
		Count = 0;
		Ratio = GTimeRatio::Second;
	}
	
	// Description: Constructor
	// Return:
	// Notes:
	// -----------------------------------------------------
	GTimeStamp::GTimeStamp(GUInt64 TheCount, const GTimeRatio& TheRatio)
	{
		Count = TheCount;
		Ratio = TheRatio;
	}
	
	// Description: Destructor
	// Return:
	// Notes:
	// -----------------------------------------------------
	GTimeStamp::~GTimeStamp()
	{
	}

	// Description: double cast operator
	// Return: returns stored time in seconds
	// Notes1: e.g. 3.000 if count is 3 and Ratio is GTimeRatio::Second
	// Notes2: e.g. 0.003 if count is 3 and Ratio is GTimeRatio::MiliSecond
	// -----------------------------------------------------
	GTimeStamp::operator double () const
	{
		return GetSeconds();
	}

	// Description: Returns time in seconds
	// Return:
	// Notes1: e.g. 3.000 if count is 3 and Ratio is GTimeRatio::Second
	// Notes2: e.g. 0.003 if count is 3 and Ratio is GTimeRatio::MiliSecond
	// -----------------------------------------------------
	double GTimeStamp::GetSeconds() const
	{
		return (double)Count * (double)Ratio;
	}

	/****************************************************/
	/*						GTimer						*/
	/****************************************************/

	// Description: Default constructor
	// Return:
	// Notes: Defaults are Enabled:true, Reoccur:false, Interval:One Second
	// -----------------------------------------------------
	GTimer::GTimer()
	{
		Enabled = true;
		Reoccur = false;
		Interval = GTimeStamp(1, GTimeRatio::Second);
	}
		
	// Description: Constructor
	// Return:
	// Notes:
	// -----------------------------------------------------
	GTimer::GTimer(bool Enabled, bool Reoccur, const GTimeStamp& Interval)
	{
		this->Enabled = Enabled;
		this->Reoccur = Reoccur;
		this->Interval = Interval;
	}

	// Description: Destructor
	// Return:
	// Notes:
	// -----------------------------------------------------
	GTimer::~GTimer()
	{
	}

	// Description: Sets Reoccuring on or of
	// Return:
	// Notes1: Reoccur is used for both chronometer and callback calling
	// Notes2: If reoccur is false then timer will only run once and then switch to disabled
	// -----------------------------------------------------
	void GTimer::SetReoccur(bool Value)
	{
		Reoccur = Value;
	}

	// Description: Returns if reocur is set or not
	// Return:
	// Notes:
	// -----------------------------------------------------
	bool GTimer::GetReoccur() const
	{
		return Reoccur;
	}

	// Description: Sets if timer is enabled or disabled
	// Return:
	// Notes: If it is disabled callback wont be called and chronometer will not work
	// -----------------------------------------------------
	void GTimer::SetEnabled(bool Value)
	{
		Enabled = Value;
	}

	// Description: Returns if timer is enabled or not
	// Return:
	// Notes:
	// -----------------------------------------------------
	bool GTimer::GetEnabled() const
	{
		return Enabled;
	}

	// Description: Sets the interval
	// Return:
	// Notes: Interval is used for timed callbacks and chronometer
	// -----------------------------------------------------
	void GTimer::SetInterval(const GTimeStamp& Stamp)
	{
		Interval = Stamp;
	}

	// Description: Returns the stored interval
	// Return:
	// Notes:
	// -----------------------------------------------------
	const GTimeStamp& GTimer::GetInterval() const
	{
		return Interval;
	}

	// Description: Calls the callback method if the interval time is passed since last reset
	// Return:
	// Notes1: If timer is not enabled this method will do nothing
	// Notes2: If reoccur is not set then timer becomes disabled after the callback is executed
	// Notes3: This method should be called in a loop to trigger the callback or register the timer to timemanager for automatic triggering
	// -----------------------------------------------------
	void GTimer::Update()
	{
		if (!Enabled)
		{
			return;
		}

		GUInt64 ElapsedTime = GetElapsedTime(Interval.Ratio);
		if (Interval.Count <= ElapsedTime)
		{
			Callback.Invoke((float)Interval.GetSeconds());
			Reset();

			if (!Reoccur)
			{
				Enabled = false;
			}
		}
	}

	// Description: Used for checking if interval time has elapsed
	// Return: returns false until the interval time passes and returns true once at every internal time.
	// Notes1: Timer is reset after interval time passes.
	// Notes1: If timer is not enabled, this method will do nothing
	// Notes2: If reoccur is not set then timer becomes disabled after the timer is reset.
	// -----------------------------------------------------
	bool GTimer::Chronometer()
	{
		if (!Enabled)
		{
			return false;
		}

		GUInt64 ElapsedTime = GetElapsedTime(Interval.Ratio);
		if (Interval.Count <= ElapsedTime)
		{
			Reset();

			if (!Reoccur)
			{
				Enabled = false;
			}

			return true;
		}

		return false;
	}

	// Description: returns elapsed time in terms of seconds
	// Return: elapsed time
	// Notes1: e.g. 2 seconds passed then return will be 2.000
	// Notes2: e.g. 2 miliseconds passed then return will be 0.002
	// -----------------------------------------------------
	double GTimer::GetElapsedSeconds()
	{
		return (double)GetElapsedTicks() / (double)TicksPerSecond;
	}

	// Description: returns elapsed time based on the precision given
	// Return: elapsed time
	// Notes1: e.g. 2 seconds passed and ratio is GTimeRatio::Second then return will be 2
	// Notes2: e.g. 2 seconds passed and ratio is GTimeRatio::MiliSecond then return will be 2000
	// -----------------------------------------------------
	GUInt64 GTimer::GetElapsedTime(const GTimeRatio& Ratio) const
	{
		return GetElapsedTime(GetElapsedTicks(), Ratio);
	}

	// Description: returns elapsed time based on the precision given. Static version of GTimer::GetElapsedTime()
	// Return: elapsed time
	// Notes1: e.g. 2 seconds passed and ratio is GTimeRatio::Second then return will be 2
	// Notes2: e.g. 2 seconds passed and ratio is GTimeRatio::MiliSecond then return will be 2000
	// -----------------------------------------------------
	GUInt64 GTimer::GetElapsedTime(GUInt64 ElapsedTicks, const GTimeRatio& Ratio)
	{
		return (ElapsedTicks * Ratio.Denominator) / (Ratio.Numerator * TicksPerSecond);
	}
	
}; // namespace GUtil